package org.vectrics.system;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.vectrics.SystemException;
import org.vectrics.batch.BatchInfo;
import org.vectrics.batch.BatchLog;
import org.vectrics.cms.CmsSection;
import org.vectrics.cms.ContentHelper;
import org.vectrics.common.util.FileCopy;
import org.vectrics.common.util.StringUtils;
import org.vectrics.domain.Domain;
import org.vectrics.domain.HtmlFormType;
import org.vectrics.hibernate.Filter;
import org.vectrics.hibernate.HibernateUtil;
import org.vectrics.hibernate.ThreadProperties;
import org.vectrics.mail.FileEmailTemplate;
import org.vectrics.mail.FileEmailTemplateCategory;
import org.vectrics.provider.CoreServiceLocator;

/**
 * This class provides business delegate methods related to core system activity
 * for the application.
 * 
 * @author Mike Moore
 * 
 */
public class SystemService {
    // Application specific copy utilities for data owners.
    private final Set applicationDomainCopyUtils = new HashSet();

    // Caches for system constants to save time with database lookups.
    private Map currentSystemConstants = null;

    private transient final Logger log = Logger.getLogger(this.getClass());

    private Map savedSystemConstants = null;

    public SystemService() {
    }

    public void addApplicationDomainCopyUtil(ApplicationDomainCopyUtil util) {
        this.applicationDomainCopyUtils.add(util);
    }

    public void copyAllCmsSections(Domain fromDomain, Domain toDomain) {
        this.log.info("Copying CMS content from domain: " + fromDomain + "  To domain: " + toDomain);
        Iterator iter = this.findAllCmsSections(fromDomain);
        while (iter.hasNext()) {
            CmsSection section = (CmsSection) iter.next();
            CmsSection newSection = new CmsSection();
            newSection.setDomain(toDomain);
            newSection.setDescription(section.getDescription());
            newSection.setCode(section.getCode());
            newSection.setUserPrivilegeType(section.getUserPrivilegeType());
            ThreadProperties.getSession().save(newSection);
        }

        String fromDir = CoreServiceLocator.getConfiguration().getDynamicContentPath(fromDomain);
        String toDir = CoreServiceLocator.getConfiguration().getDynamicContentPath(toDomain);
        this.log.info("Copying cms from: " + fromDir + "\r\n -  To dir: " + toDir);
        FileCopy.copyDirectory(fromDir, toDir);

    }

    /**
     * Copy html display settings from one data owner to the other.
     * 
     * @param fromDomain
     * @param toDomain
     */
    public void copyHtmlSettings(Domain fromDomain, Domain toDomain) {
        this.log.info("Copying HTML setting from domain: " + fromDomain + "  To domain: " + toDomain);
        String fromDir = CoreServiceLocator.getConfiguration().getHtmlStyleDirectory();
        if (fromDir.endsWith("/") == false) {
            fromDir += "/";
        }
        fromDir += fromDomain.getId().toString();

        String toDir = CoreServiceLocator.getConfiguration().getHtmlStyleDirectory();
        if (toDir.endsWith("/") == false) {
            toDir += "/";
        }
        toDir += toDomain.getId().toString();

        this.log.info("Copying html files from: " + fromDir + "\r\n -  To dir: " + toDir);
        FileCopy.copyDirectory(fromDir, toDir);

    }

    /**
     * Copy html display settings from one data owner to the other.
     * 
     * @param fromDomain
     * @param toDomain
     */
    public void copyMenuConfigs(Domain fromDomain, Domain toDomain) {
        this.log.info("Copying menu configurations from domain: " + fromDomain + "  To domain: " + toDomain);

        File fromDir = CoreServiceLocator.getConfiguration().getMenuConfigDirectory(fromDomain);
        File toDir = CoreServiceLocator.getConfiguration().getMenuConfigDirectory(toDomain);

        this.log.info("Copying menu config files from: " + fromDir + "\r\n -  To dir: " + toDir);
        FileCopy.copyDirectory(fromDir, toDir);

    }

    /**
     * Copy settings from parent data owner, blank out values for system
     * constants that have 'canPassToChildren' = false.
     * 
     * @param fromDomain
     *            The data owner that is the source of the system constants.
     * @param toDomain
     *            The data owner that will receive the new system constants.
     */
    public void copySystemConstants(Domain fromDomain, Domain toDomain) {
        Filter filter = new Filter();
        filter.addSelectField("systemConstant");
        filter.addFrom("SystemConstant", "systemConstant");
        filter.addCondition("systemConstant.global='F'");
        filter.addCondition("systemConstant.canPassToChildren='T'");
        filter.addParameterCondition("systemConstant.domain.id", "=", fromDomain.getId());

        List fromList = filter.createQuery(ThreadProperties.getSession()).list();

        Iterator iter = fromList.iterator();
        while (iter.hasNext()) {
            SystemConstant systemConstant = (SystemConstant) iter.next();
            SystemConstant toSystemConstant = new SystemConstant();
            toSystemConstant.setCode(systemConstant.getCode());
            toSystemConstant.setDomain(toDomain);
            toSystemConstant.setDescription(toDomain.getDescription());
            toSystemConstant.setGlobal(false);
            toSystemConstant.setType(systemConstant.getType());
            if (systemConstant.getCanPassToChildren()) {
                toSystemConstant.setValue(systemConstant.getValue());
            } else {
                toSystemConstant.setValue("");
            }

            ThreadProperties.getSession().save(toSystemConstant);
        }
    }

    /**
     * Save the passed log entry for a batch that is recording log information.
     * 
     * @param log
     *            The log entry to save.
     */
    public void createBatchLog(BatchLog log) {
        Session session = null;
        if (ThreadProperties.getPerson() == null) {
            ThreadProperties.setPerson(CoreServiceLocator.getConfiguration().getBatchPerson());
        }

        session = ThreadProperties.getSession();
        session.save(log);
    }

    /**
     * Returns all CMS Sections for the data owner.
     * 
     * @param domain
     *            The data owner.
     * @return
     */
    public Iterator findAllCmsSections(Domain domain) {
        ContentHelper contentHelper = CoreServiceLocator.getContentHelper();
        return (contentHelper.findAllCmsSections(domain));
    }

    /**
     * Finds all data owners for the application.
     * 
     * @return
     */
    public List findAllDomains() {
        List allDomains;
        Session session = null;
        session = ThreadProperties.getSession();
        allDomains = session.createQuery("from Domain domain where domain.voided='F' order by domain.id").list();
        return (allDomains);
    }

    public List<FileEmailTemplateCategory> findAllEmailTemplateCategories(Domain domain) {
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplateCategory", "category");
        filter.addSelectField("category");
        filter.addParameterCondition("category.domain.id", "=", domain.getId());
        filter.addOrderBy("category.description");
        Query query = filter.createQuery(ThreadProperties.getSession());
        return (query.list());
    }

    public List<FileEmailTemplate> findAllEmailTemplates(Domain domain) {
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplate", "template");
        filter.addSelectField("template");
        filter.addParameterCondition("template.domain.id", "=", domain.getId());
        filter.addOrderBy("template.category.description");
        Query query = filter.createQuery(ThreadProperties.getSession());
        return (query.list());
    }

    /**
     * 
     * @return
     */
    public Collection findAllHtmlFormTypes() {
        Filter filter = new Filter();
        filter.addFrom("HtmlFormType", "htmlFormType");
        filter.addSelectField("distinct htmlFormType");
        Query query = filter.createQuery(ThreadProperties.getSession());
        return (query.list());
    }

    /**
     * Returns all system constants for the data owner.
     * 
     * @param domain
     *            The data owner.
     * @param showGlobal
     *            Include system constants that are global across all data
     *            owners.
     * @return
     */
    public Iterator findAllSystemConstants(Domain domain, boolean showGlobal) {
        Filter filter = new Filter();
        filter.addSelectField("systemConstant");
        filter.addFrom("SystemConstant", "systemConstant");
        if (showGlobal == false) {
            filter.addCondition("systemConstant.global='F'");
        }
        filter.addParameterCondition("systemConstant.domain.id", "=", domain.getId());
        filter.addOrderBy("systemConstant.code");

        Iterator list = filter.createQuery(ThreadProperties.getSession()).iterate();
        return (list);
    }

    public BatchInfo findBatchInfo(Long id) {
        return (BatchInfo) ThreadProperties.getSession().load(BatchInfo.class, id);
    }

    /**
     * Return the CmsSection object for the domain and section code passed.
     * 
     * @param domain
     * @param sectionCode
     * @return
     */
    public CmsSection findCmsSection(Domain domain, String sectionCode) {
        ContentHelper contentHelper = CoreServiceLocator.getContentHelper();
        return (contentHelper.findCmsSection(domain, sectionCode));
    }

    /**
     * Find the Cascading Style Sheet entries for a particular type of form for
     * a particular data owner.
     * 
     * @param domain
     * @param formType
     * @return
     */
    public Collection findCssEntries(Domain domain, HtmlFormType formType) {
        if (domain == null) {
            domain = this.findSystemDomain();
        }
        Collection entries;
        Session session = null;
        session = ThreadProperties.getSession();
        Query query = session.createQuery("select htmlCssEntry from HtmlCssEntry htmlCssEntry where "
                + "htmlCssEntry.domain.id=? and htmlCssEntry.htmlFormType=?");

        query.setLong(0, domain.getId().longValue());
        query.setString(1, formType.getCode());
        entries = query.list();
        return (entries);
    }

    /**
     * Find a data owner by its ID.
     * 
     * @param id
     * @return
     */
    public Domain findDomain(Long id) {
        Domain domain = (Domain) ThreadProperties.getSession().get(Domain.class, id);
        if (domain.getVoided()) {
            throw new SystemException("Data owner specified in web call is voided");
        }
        return (domain);
    }

    /**
     * Find the domain with the passed name.
     * 
     * @param soughtName
     *            The name to find
     * @return
     */
    public Domain findDomainForName(String soughtName) {
        Filter filter = new Filter();
        filter.addFrom("Domain", "domain");
        filter.addSelectField("domain");
        filter.addParameterCondition("domain.description", "=", soughtName);
        filter.addCondition("domain.voided='F'");
        Iterator iter = filter.createQuery(ThreadProperties.getSession()).iterate();
        Domain domain = null;
        if (iter.hasNext()) {
            domain = (Domain) iter.next();
        }
        return (domain);
    }

    public List<FileEmailTemplate> findEmailTemplatesForCategory(Domain domain, FileEmailTemplateCategory category) {
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplate", "template");
        filter.addSelectField("template");
        filter.addParameterCondition("template.category.id", "=", category.getId());
        filter.addParameterCondition("template.domain.id", "=", domain.getId());

        return filter.createQuery(ThreadProperties.getSession()).list();
    }

    /**
     * Busca el fileEmailTempate segun el name pasado por parametro si no lo
     * encuentra devuelve null.
     * 
     * @param domain
     * @param string
     * @return fileEmailTempate, si no lo encuentra devuelve null.
     */
    public FileEmailTemplate findEmailTemplatesForName(Domain domain, String name) {
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplate", "template");
        filter.addSelectField("template");
        filter.addParameterCondition("template.domain.id", "=", domain.getId());
        filter.addParameterCondition("template.name", "=", name);

        Iterator iter = filter.createQuery(ThreadProperties.getSession()).iterate();
        if (iter.hasNext()) {
            return ((FileEmailTemplate) iter.next());
        } else {
            return null;
        }
    }

    public FileEmailTemplate findFileEmailTemplate(FileEmailTemplateCategory category, String name) {
        this.log.info("Find email template for category and name" + "- category: " + category + "- template name: "
                + name);
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplate", "template");
        filter.addSelectField("template");
        filter.addParameterCondition("template.category.id", "=", category.getId());
        filter.addParameterCondition("template.name", "=", name);

        Iterator iter = filter.createQuery(ThreadProperties.getSession()).iterate();
        if (iter.hasNext()) {
            return ((FileEmailTemplate) iter.next());
        } else {
            return null;
        }
    }

    /**
     * Find category corresponding to the code
     * 
     * @param code
     * @return
     */
    public FileEmailTemplateCategory findFileEmailTemplateCategory(Domain domain, String code) {
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplateCategory", "category");
        filter.addSelectField("category");
        filter.addParameterCondition("category.domain.id", "=", domain.getId());
        filter.addParameterCondition("category.code", "=", code);
        Query query = filter.createQuery(ThreadProperties.getSession());
        Iterator iter = query.iterate();
        FileEmailTemplateCategory category = null;

        if (iter.hasNext()) {
            category = (FileEmailTemplateCategory) iter.next();
        }
        return (category);
    }

    public FileEmailTemplateCategory findFileEmailTemplateCategory(Long id) {
        return ((FileEmailTemplateCategory) ThreadProperties.getSession().load(FileEmailTemplateCategory.class, id));
    }

    public FileEmailTemplate findFileEmailTemplateWithFileName(FileEmailTemplateCategory category, String fileName) {
        this.log.info("Find email template for category and name" + "- category: " + category + "- template name: "
                + fileName);
        Filter filter = new Filter();
        filter.addFrom("FileEmailTemplate", "template");
        filter.addSelectField("template");
        filter.addParameterCondition("template.category.id", "=", category.getId());
        filter.addParameterCondition("template.fileName", "=", fileName);

        Iterator iter = filter.createQuery(ThreadProperties.getSession()).iterate();
        if (iter.hasNext()) {
            return ((FileEmailTemplate) iter.next());
        } else {
            return null;
        }
    }

    public FileEmailTemplate findFileEmailTemplateWithId(Long templateId) {
        return (FileEmailTemplate) ThreadProperties.getSession().load(FileEmailTemplate.class, templateId);
    }

    public SystemConstant findGlobalSystemConstant(String code) {
        HibernateUtil hibernate = (HibernateUtil) CoreServiceLocator.findServiceBean("hibernate.util");
        Session session = hibernate.createNewSession();

        SystemConstant constant = null;
        try {
            Filter filter = new Filter();
            filter.addFrom("SystemConstant", "systemConstant");
            filter.addSelectField("systemConstant");
            filter.addParameterCondition("systemConstant.code", "=", code);

            if (this.log.isDebugEnabled()) {
                this.log.debug("findGlobalSystemConstant() - Statement = " + filter.getStatement());
            }

            Query query = filter.createQuery(session);
            List allConstantsList = query.list();

            if (allConstantsList.size() > 1) {
                throw new SystemException("Found more than one global system" + " constant for code: " + code);
            }
            if (allConstantsList.size() < 1) {
                throw new SystemException("Could not find global system" + " constant for code: " + code
                        + "\r\n - sql = " + filter.getStatement());
            }

            Iterator iter = allConstantsList.iterator();
            while (iter.hasNext()) {
                constant = (SystemConstant) iter.next();
            }
        } catch (Exception ex) {
            String message = "Exception finding global sys constant: " + code + "  Application context: "
                    + ThreadProperties.getApplicationContext().getName();
            this.log.error(message, ex);
            throw new SystemException(message, ex);
        }

        if (constant.getGlobal() == false) {
            throw new SystemException("findGlobalSystemConstant() method found a system constant that was not global: "
                    + constant.getCode());
        }
        return (constant);

    }

    public HtmlFormType findHtmlFormType(String code) {
        Session session = null;
        session = ThreadProperties.getSession();
        return (HtmlFormType) session.load(HtmlFormType.class, code);
    }

    public SystemConstant findSystemConstant(Domain pDomain, String code) {
        Domain domain = pDomain;
        if (domain == null) {
            domain = ThreadProperties.getDomain();
        }
        if (domain == null) {
            throw new SystemException("Domain is null");
        }

        if (this.currentSystemConstants == null) {
            this.currentSystemConstants = new HashMap();
            this.savedSystemConstants = null;
            Query query = ThreadProperties.getSession().createQuery("select const" + " from SystemConstant" + " const");
            Iterator allConstantsIter = query.iterate();
            while (allConstantsIter.hasNext()) {
                SystemConstant constant = (SystemConstant) allConstantsIter.next();
                this.addSystemConstantToCache(constant);
            }
        }

        String sysConstantKey = this.makeSystemConstantKey(domain, code);
        this.log.debug("Seeking key: " + sysConstantKey);
        SystemConstant constant = (SystemConstant) this.currentSystemConstants.get(sysConstantKey);
        if (constant == null) {
            this.log.debug("Search for constant in database and add to cache if found");
            constant = this.findSystemConstantInDatabase(domain, code);
        }
        if (constant == null) {
            throw new SystemException("Could not find system constant for: " + code + " Domain: "
                    + domain.getId().toString() + " - " + domain.getDescription() + "\r\n  - Key = " + sysConstantKey);
        }
        return (constant);
    }

    public Domain findSystemDomain() {
        Domain domain = this.findDomain(new Long(1));
        return (domain);
    }

    public Collection getAllPageTitles() {
        Collection allPageTitles;
        Session session = null;
        session = ThreadProperties.getSession();
        allPageTitles = session.createQuery("from PageTitle pageTitle").list();
        return (allPageTitles);
    }

    /**
     * Return a system constant value that is a boolean. The string value of the
     * system constant should be 'true' or 'false'.
     * 
     * @param code
     * @param domain
     * @return
     */
    public Boolean getBooleanSystemConstant(String code, Domain domain) {
        SystemConstant constant = this.findSystemConstant(domain, code);
        return (constant.getBooleanValue());
    }

    public Long getLongSystemConstant(String code, Domain domain) {
        SystemConstant constant = this.findSystemConstant(domain, code);
        return (constant.getLongValue());
    }

    public Long getLongSystemConstantGlobal(String code) {
        SystemConstant constant = this.findGlobalSystemConstant(code);
        return (constant.getLongValue());
    }

    /**
     * Return the string values for a particular system constant.
     * 
     * @param code
     * @param domain
     * @return
     */
    public String getStringSystemConstant(String code, Domain domain) {
        SystemConstant constant = this.findSystemConstant(domain, code);
        return (constant.getValue());
    }

    public void restoreSystemConstants() {
        if (this.savedSystemConstants == null) {
            throw new SystemException("restoreSystemConstants() called, but saveSystemConstants() "
                    + "was never called previously.");
        }
        this.currentSystemConstants = this.savedSystemConstants;
        this.savedSystemConstants = null;
    }

    public void saveBatchInfo(BatchInfo info) {
        Session session = ThreadProperties.getSession();
        if (StringUtils.isNullOrBlank(info.getBatchTypeCode())) {
            throw new SystemException("Batch Type Code not defined");
        }
        if (ThreadProperties.getPerson() == null) {
            ThreadProperties.setPerson(CoreServiceLocator.getConfiguration().getBatchPerson());
        }
        session.save(info);
    }

    public void saveSystemConstants() {
        this.savedSystemConstants = new HashMap();
        this.savedSystemConstants.putAll(this.currentSystemConstants);
    }

    public void setSystemConstantValue(Domain domain, String code, String value) {
        SystemConstant sysConstant = this.findSystemConstant(domain, code);
        sysConstant.setValue(value);
    }

    /**
     * Save the batch information. This is instance data from a batch run.
     * 
     * @param info
     *            The batch information to save.
     */
    public void updateBatchInfo(BatchInfo info) {
        Session session = null;
        session = ThreadProperties.getSession();
        // TransactionToken txToken = ThreadProperties.beginTransaction();
        if (ThreadProperties.getPerson() == null) {
            ThreadProperties.setPerson(CoreServiceLocator.getConfiguration().getBatchPerson());
        }
        session.update(info);
        // ThreadProperties.commitTransaction(txToken);
    }

    /**
     * Save changes to a data owner, if data owner is new, copy attributes from
     * parent data owner and call applcation specific handlers to do their own
     * copying.
     * 
     * @param domain
     */
    public void updateDomain(Domain domain) {
        boolean newDomain = false;
        if (domain.getParent() == null) {
            throw new SystemException(
                    "Cannot save domain without parent data owner, unless domain is the 'system domain' - which cannot be added by application.");
        }

        if (domain.getId() == null) {
            newDomain = true;
            if (domain.getParent() == null) {
                throw new SystemException("New data owners must have a parent data owner");
            }
        }

        ThreadProperties.getSession().saveOrUpdate(domain);
        if (newDomain) {
            this.log.info("New domain created: " + domain + "  - copying setting from parent domain.");
            CoreServiceLocator.getLocationService().copyLocations(domain.getParent(), domain);
            CoreServiceLocator.getSystemService().copySystemConstants(domain.getParent(), domain);
            CoreServiceLocator.getSystemService().copyHtmlSettings(domain.getParent(), domain);
            CoreServiceLocator.getSystemService().copyMenuConfigs(domain.getParent(), domain);
            CoreServiceLocator.getSystemService().copyAllCmsSections(domain.getParent(), domain);

            Properties copyProperties = new Properties();

            Iterator iter = this.applicationDomainCopyUtils.iterator();
            while (iter.hasNext()) {
                ApplicationDomainCopyUtil copyUtil = (ApplicationDomainCopyUtil) iter.next();
                copyUtil.execute(domain.getParent(), domain, copyProperties);
            }
        } else {
            this.log.info("Update to existing domain - no setting copy");
        }
    }

    public void updateFileEmailTemplate(FileEmailTemplate template) {
        ThreadProperties.getSession().saveOrUpdate(template);
    }

    public void updateFileEmailTemplateCategory(FileEmailTemplateCategory category) {
        ThreadProperties.getSession().update(category);
    }

    public void updateSystemConstant(SystemConstant systemConstant) {
        Session session = ThreadProperties.getSession();

        session.saveOrUpdate(systemConstant);

        this.log.debug("Add system constant to cached list if it is not already there");
        if (this.findSystemConstant(systemConstant.getDomain(), systemConstant.getCode()) == null) {
            this.addSystemConstantToCache(systemConstant);
        }
    }

    protected Domain getDefaultDomain() {
        return (ThreadProperties.getDomain());
    }

    private void addSystemConstantToCache(SystemConstant systemConstant) {
        String key = this.makeSystemConstantKey(systemConstant.getDomain(), systemConstant.getCode());
        this.currentSystemConstants.remove(systemConstant);
        this.currentSystemConstants.put(key, systemConstant);
        this.log.debug("Add sys const: " + key + "    Value: " + systemConstant.getValue());
    }

    private SystemConstant findSystemConstantInDatabase(Domain domain, String code) {
        SystemConstant systemConstant = null;
        Filter filter = new Filter();
        filter.addFrom("SystemConstant", "systemConstant");
        filter.addSelectField("systemConstant");
        filter.addParameterCondition("systemConstant.domain.id", "=", domain.getId());
        filter.addParameterCondition("systemConstant.code", "=", code);

        Query query = filter.createQuery(ThreadProperties.getSession());
        List list = query.list();
        if (list.size() > 1) {
            throw new SystemException("Found more than one sys const that matched data owner, and code: "
                    + domain.getId() + "  code: " + code);
        }
        if (list.size() == 1) {
            systemConstant = (SystemConstant) list.iterator().next();
        }
        return (systemConstant);
    }

    private String makeSystemConstantKey(Domain domain, String code) {
        return (domain.getId().toString() + "|" + code);
    }
}
